package com.ek.kademlia.task;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import com.ek.common.Bucket;
import com.ek.common.Context;
import com.ek.common.FutureResponse;
import com.ek.common.MessageSender;
import com.ek.kademlia.ObservableBucket;
import com.ek.kademlia.ObservableLinkedList;
import com.ek.kademlia.protocol.impl.DefaultFindNodeRequest;
import com.ek.kademlia.protocol.impl.DefaultFindNodeResponse;
import com.ek.service.mina.ComunicationService;
import com.ek.util.Utils;

public class NodeLookup extends FutureTask<List<Bucket>> {

    private static final int alpha = 3;

    public NodeLookup(final byte[] query, final Context context) {
        super(new Callable<List<Bucket>>() {

            private ObservableLinkedList<ObservableBucket> actual = new ObservableLinkedList<ObservableBucket>();
            private ObservableLinkedList<ObservableBucket> previous = new ObservableLinkedList<ObservableBucket>();

            public List<Bucket> call() throws Exception {
                actual.addAllIfNotContained(getObservableBuckets(context.getRoutingTable().getClosestBucket(query, alpha)));
                while (!actual.equals(previous)) {
                    nextStep();
                    List<FutureResponse<DefaultFindNodeResponse>> results = new ArrayList<FutureResponse<DefaultFindNodeResponse>>();
                    sendFindNodes(query, context.getComunicationService(), results);
                    collectResults(results);
                }
                return new LinkedList<Bucket>(actual);
            }

            private void nextStep() {
                previous = actual;
                actual = new ObservableLinkedList<ObservableBucket>(previous);
            }

            private void sendFindNodes(final byte[] query, final MessageSender sender, List<FutureResponse<DefaultFindNodeResponse>> results) throws NoSuchAlgorithmException,
                    UnsupportedEncodingException {
                ObservableBucket removed;
                DefaultFindNodeRequest request;
                int i = 0;
                while (i < alpha && i < actual.size()) {
                    removed = actual.getFirstNotSeen();
                    if (removed != null) {
                        removed.setAlreadySeen(true);
                        request = new DefaultFindNodeRequest(context.getNodeID(), Utils.computeSHA1FromString(UUID.randomUUID().toString()), query);
                        FutureResponse<DefaultFindNodeResponse> result = sender.sendMessage(request, new InetSocketAddress(removed.getAddress(), ComunicationService.PORT));
                        results.add(result);
                        ++i;
                    } else {
                        break;
                    }
                }
            }

            private void collectResults(List<FutureResponse<DefaultFindNodeResponse>> results) {
                int i = 0;
                while (i < alpha && i < results.size()) {
                    FutureResponse<DefaultFindNodeResponse> result = results.get(i);
                    DefaultFindNodeResponse response = result.get(3, TimeUnit.SECONDS);
                    if (response != null) {
                        List<Bucket> buckets = response.getBuckets();
                        actual.addAllIfNotContained(getObservableBuckets(buckets));
                    } else {
                        deleteBadNode();
                    }
                    ++i;
                }
            }

            private void deleteBadNode() {
                // ??
                // previous.remove();
                // actual.remove();
            }

            private Collection<ObservableBucket> getObservableBuckets(Collection<Bucket> buckets) {
                LinkedList<ObservableBucket> observableBuckets = new LinkedList<ObservableBucket>();
                for (Bucket bucket : buckets) {
                    observableBuckets.add(new ObservableBucket(bucket));
                }
                return observableBuckets;
            }
        });
    }
}
